/**
 * Author(s): xuming.Wong <xuming.Wong@gmail.com>
 */
#include "GPKArchive.h"

#include "GPKArchiveManager.h"//for search the archive parent folder
#include "FileSystem.h"
#include "DataSerializer.h"

using namespace PQCore;

namespace PQEngine
{
	const std::string GPKArchive::MAGIC=".gpk";//magic is also ext
	const std::string GPKArchive::EXT_GPK="gpk";
	const std::string GPKArchive::EXT_META="gpm";//short for GPK Meta

	GPKArchive::ArchiveMap GPKArchive::_map;

	GPKArchive::~GPKArchive()
	{
	}

	GPKArchive::GPKArchive(std::string path,bool useAssistFile)
		: _path(path),_useAssistFile(useAssistFile)
	{
		int headerSize=sizeof(GPKHeader);

		if(!GFileBaseSystem.isFileExist(_path)){//not exist create
			GFileBaseSystem.createFile(_path);
			
			//write header to .gpk
			std::ofstream outstream;
			outstream.open(_path.c_str(),std::ios::binary);
			outstream.seekp(0,std::ios::beg);
			outstream.write((char*)(&_header),headerSize);
			outstream.close();
		}else{
			//file exist read
			std::ifstream instream;
			instream.open(_path.c_str(),std::ios::binary);
			instream.seekg(0,std::ios::beg);
			
			instream.read((char*)(&_header),headerSize);
			instream.close();
		}
		
		assert(_header.magic[0]=='.'&&
			_header.magic[1]=='g'&&
			_header.magic[2]=='p'&&
			_header.magic[3]=='k');


		_assitFilePath=FileSystem::changeExt(_path,EXT_META);

		if(!_useAssistFile){
			return;
		}

		//init archives using data in GAssets.gdt
		SerializeParser parser(_assitFilePath);
		if(!parser.success()){
			return;
		}

		SerializeDataId gname=parser.getName();
		if(gname==SerializeDataId_GPKAsset){
			int size=parser.getParam()->getInt();
			for(int i=0;i<size;i++){
				std::string fname=parser.getParam()->getString();
				_fileNames.push_back(fname);
				
			}
		}

		initGPKFiles();
	}

	void GPKArchive::initGPKFiles()
	{
		int cnt=_header.numFile;
		
		assert(_fileNames.size()==cnt);

		for(int i=0;i<cnt;i++){	
			int fileSize=_header.infos[i].size;
			GPKFile file(this,_fileNames[i], i,fileSize);
			_files[i]=file;
		}

	}

	GPKArchive* GPKArchive::get(std::string path)
	{	
		ArchiveMap::iterator it= _map.find(path);
		if(it!=_map.end()){
			return it->second;
		}

		GPKArchive* archive=new GPKArchive(path);
		_map.insert(ArchiveMap::value_type(path,archive));

		return archive;
	}

	bool GPKArchive::addFile(std::string filePath)
	{
		GFileType fileType= FileSystem::getFileTypeByName(filePath);

		std::string name=FileSystem::getPathLastNode(filePath);

		int index=getFileIndex(name);
		if(index!=-1){//file already in .gpk archive
			return false;
		}

		//read data from local file system
		std::ifstream instream;
		instream.open(filePath.c_str(),std::ios::binary);//open local system file----
		instream.seekg(0,std::ios::end);
		int fileSize=instream.tellg();
		

		//update gpack header
		_header.numFile++;
		int fileIndex=getGPKFreeSlot();
		assert(fileIndex>=0);
		if(fileIndex==0){
			_header.infos[fileIndex].offset=sizeof(GPKHeader);//first one offset is always this
		}else if(fileIndex>0){//>0
			_header.infos[fileIndex].offset=_header.infos[fileIndex-1].offset+_header.infos[fileIndex-1].size;
		}
		_header.infos[fileIndex].size=fileSize;
		_header.infos[fileIndex].state=1;
		_header.infos[fileIndex].id=fileIndex;
		_header.infos[fileIndex].type=(int)fileType;
		//write data to .gpk archive,error overide old data to 0?
		std::fstream outstream;
		outstream.open(_path.c_str(),std::fstream::in | std::fstream::out | std::fstream::binary);

		//write GPK header
		outstream.seekp(0,std::ios::beg);
		outstream.write((char*)(&_header),sizeof(GPKHeader));
		

		//write file data
		int fdataoffset=_header.infos[fileIndex].offset;
		outstream.seekp(fdataoffset,std::ios::beg);
		instream.seekg(0,std::ios::beg);
		char buf[GPACK_FILE_COPY_BUF];
		int cnt= (float)fileSize/(float)(GPACK_FILE_COPY_BUF);
		
		for(int i=0;i<cnt;i++){
			instream.read(buf,GPACK_FILE_COPY_BUF);
			outstream.write(buf,GPACK_FILE_COPY_BUF);
		}

		int leftBytes=fileSize-cnt*GPACK_FILE_COPY_BUF;
		instream.read(buf,leftBytes);
		outstream.write(buf,leftBytes);

		outstream.close();
		instream.close();////close local system file----


		_fileNames.push_back(name);
		_files[fileIndex]=GPKFile(this,name,fileIndex,fileSize);

		persistMetaFile();
		//
		return true;

	}

	void GPKArchive::persistMetaFile()
	{
		SerializeGenerator gen(SerializeDataId_GPKAsset);

		int size=_fileNames.size();
		gen.addIntParam(size);
		for(int i=0;i<size;i++){
			gen.addStringParam(_fileNames[i]);
			
		}
		
		gen.serialize(_assitFilePath);
	}

	int GPKArchive::getGPKFreeSlot()
	{
		int saved=-1;
		for(int i=0;i<GPACK_FILE_LIMIT;i++){
			int state=_header.infos[i].state;
			if(state==0){
				return i;
			}
			//
			if(saved!=-1){//already find 
				continue;
			}

			if(state==2){
				saved=i;
			}
		}

		return saved;//-1 means not find and have exceed 4K files,shouldn't be

	}
	
	/*Get file in .gpk file fileIndex,now only the index of file array.
	@param fileName- in archive,now only one level names
	*/
	int GPKArchive::getFileIndex(std::string fileName)
	{
		int size=_fileNames.size();//this search is time waste!so using file index to find file as many as possible
		for(int i=0;i<size;i++){
			if(_fileNames[i]==fileName){
				return i;
			}
		}
		
		return -1;
	}

	GPKFile* GPKArchive::getFile(std::string fileName)
	{
		int index=getFileIndex(fileName);
		if(index==-1){
			return 0;
		}

		return getFile(index);
	}

	GPKFile* GPKArchive::getFile(int index)
	{
		if(index>=0&&index<_header.numFile){
			return _files+index;
		}
		return 0;
	}


	bool GPKArchive::hasFile(std::string fileName)
	{
		int id=getFileIndex(fileName);
		if(id==-1){
			return false;
		}

		return true;
	}

	void GPKArchive::readFileData(int fileIndex,int offset,char* buf,int size)
	{
		//read data from .gpk archive
		std::ifstream instream;
		instream.open(_path.c_str(),std::ios::binary);

		int realOffset=_header.infos[fileIndex].offset+offset;

		instream.seekg(realOffset,std::ios::beg);
		instream.read(buf,size);
		instream.close();
	}

	bool GPKArchive::isFileExist(std::string fileName)
	{
		int index=getFileIndex(fileName);
		if(index==-1){
			return false;
		}
		return true;
	}

}
